<template>
    <div class="setting-container">
        <n-table v-if="model && model.CPU && (model?.CPU?.InputPins.length + model?.CPU?.OutputPins.length > 0)" :bordered="false" :single-line="false" size="small">
            <thead>
                <tr>
                    <th width="3">No.</th>
                    <th>Cpu Pin</th>
                    <th width="3">Bit Size</th>
                    <th width="3">Direction</th>
                    <th>Device Pin Mapping</th>
                    <th width="5">Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="pin,idx in pinMappings" :key="idx">
                    <td>{{idx+1}}</td>
                    <td>{{pin.pinDef.Name}}</td>
                    <td>{{pin.pinDef.BitSize}}</td>
                    <td>{{pin.pinDef.Direction == 0 ? 'Input' : 'Output'}}</td>
                    <td>
                        <n-flex vertical v-for="p in pin.pinMap">
                            <span>
                                {{ getMappingTdInfo(pin,p) }}
                            </span>
                        </n-flex>
                    </td>
                    <td>
                        <n-popover trigger="hover">
                            <template #trigger>
                                <n-button  class="opbtn" strong secondary circle type="primary" size="small" @click="editPin(pin.pinDef.Name)">
                                    <template #icon>
                                        <n-icon><CreateSharp /></n-icon>
                                    </template>
                                </n-button>
                            </template>
                            <span>edit pin mapping info</span>
                        </n-popover>
                        <n-popover v-if="pin.pinMap.length > 0" trigger="hover">
                            <template #trigger>
                                <n-popconfirm
                                    @positive-click="clearPin(pin.pinDef.Name)"
                                >
                                    <template #trigger>
                                        <n-button  class="opbtn" strong secondary circle type="error" size="small">
                                            <template #icon>
                                                <n-icon><RemoveCircleSharp /></n-icon>
                                            </template>
                                        </n-button>
                                    </template>
                                    are you sure to remove this pin mapping info?
                                </n-popconfirm>
                            </template>
                            <span>clear pin mapping info</span>
                        </n-popover>
                    </td>
                </tr>
            </tbody>
        </n-table>
        <div v-else>
            <span class="errortips">Please compile and synthesize the FPGA module code first.</span>
        </div>
        <n-modal class="editmodal" :mask-closable="false" 
            v-model:show="showEdit" 
            preset="dialog" 
            title="Pin Mapping Config" 
            positive-text="Save" 
            negative-text="Cancel" 
            @positive-click="SaveEdit" >
            <n-table class="innertable" :bordered="false" :single-line="false" size="small">
                <thead>
                    <tr>
                        <th style="min-width:200px;">Input</th>
                        <th style="width:30px;">InputStartIdx</th>
                        <th style="width:30px;">InputEndIdx</th>
                        <th style="min-width:200px;">Output</th>
                        <th style="width:30px;">OutputStartIdx</th>
                        <th style="width:30px;">OutputEndIdx</th>
                    </tr>
                </thead>
                <tbody>
                    <tr v-for="item,idx in curEditPinMap" :key="idx">
                        <template v-if="curEditPin?.pinDef.Direction == 1">
                            <td>{{curEditPin?.pinDef.Name}}</td>
                            <td><n-input-number :status="inputBitStatus" :min="0" :max="getCpuStartEndIdx(curEditPin!.pinDef.Name)[1]" v-model:value="item.PinBitPair!.InputBitStart" size="small" :show-button="false" /></td>
                            <td><n-input-number :status="inputBitStatus" :min="0" :max="getCpuStartEndIdx(curEditPin!.pinDef.Name)[1]" v-model:value="item.PinBitPair!.InputBitEnd" size="small" :show-button="false" /></td>
                        </template>
                        <template v-else>
                            <td><n-select :status="inputStatus" :filterable="true" size="small" v-model:value="curEditPinMapOptIdx[idx]" :options="freeOutputPinNames" @update:value="updateLine(idx)"></n-select></td>
                            <td><n-input-number :status="inputBitStatus" :min="0" :max="getEndIdx(idx)" v-model:value="item.PinBitPair!.InputBitStart" size="small" :show-button="false" /></td>
                            <td><n-input-number :status="inputBitStatus" :min="0" :max="getEndIdx(idx)" v-model:value="item.PinBitPair!.InputBitEnd" size="small" :show-button="false" /></td>
                        </template>
                        <template v-if="curEditPin?.pinDef.Direction == 0">
                            <td>{{curEditPin?.pinDef.Name}}</td>
                            <td><n-input-number :status="outputBitStatus" :min="0" :max="getCpuStartEndIdx(curEditPin!.pinDef.Name)[1]" v-model:value="item.PinBitPair!.OutputBitStart" size="small" :show-button="false" /></td>
                            <td><n-input-number :status="outputBitStatus" :min="0" :max="getCpuStartEndIdx(curEditPin!.pinDef.Name)[1]" v-model:value="item.PinBitPair!.OutputBitEnd" size="small" :show-button="false" /></td>
                        </template>
                        <template v-else>
                            <td><n-select :status="outputStatus" :filterable="true" size="small" v-model:value="curEditPinMapOptIdx[idx]" :options="freeInputPinNames" @update:value="updateLine(idx)"></n-select></td>
                            <td><n-input-number :status="outputBitStatus" :min="0" :max="getEndIdx(idx)" v-model:value="item.PinBitPair!.OutputBitStart" size="small" :show-button="false" /></td>
                            <td><n-input-number :status="outputBitStatus" :min="0" :max="getEndIdx(idx)" v-model:value="item.PinBitPair!.OutputBitEnd" size="small" :show-button="false" /></td>
                        </template>
                    </tr>
                </tbody>
            </n-table>
            <div v-show="verifyError" class="errortips">{{ verifyErrorInfo }}</div>
        </n-modal>
    </div>
</template>
<script lang="ts" setup>
import { ref, onMounted,computed } from 'vue'
import { NTable, NButton, NIcon, NPopover, NFlex, NInputNumber,NModal,NSelect,NPopconfirm } from 'naive-ui'
import { soc,pins } from '../../../wailsjs/go/models';
import type { SelectGroupOption } from 'naive-ui'
import type { FormValidationStatus } from 'naive-ui/es/form/src/interface'
import { AddCircleSharp,RemoveCircleSharp,CreateSharp,CheckmarkCircleSharp,AddCircle,ArrowDownCircleSharp,ArrowUpCircleSharp } from '@vicons/ionicons5'

interface PinMapInfo {
    devId: number,
    pinDef: pins.PinDef,
    pinMap: pins.PinMap[]
}

interface PinInfo {
    pinDef: pins.PinDef,
    typeName: string
}

const model = defineModel<soc.SocConfig>("socSetting")
const pinMapping = ref<Map<string,PinMapInfo>>(new Map<string,PinMapInfo>())
const showEdit = ref(false)
const verifyError = ref(false)
const verifyErrorInfo = ref('')
const inputStatus = ref<FormValidationStatus>('error')
const outputStatus = ref<FormValidationStatus>('error')
const inputBitStatus = ref<FormValidationStatus>('success')
const outputBitStatus = ref<FormValidationStatus>('success')
const editPinName = ref('')
const curEditPinMap = ref<pins.PinMap[]>()
const curEditPinMapOptIdx = ref<string[]>([])
const pinDefMap = new Map<string,PinInfo>()
const cpuPinDefMap = new Map<string,pins.PinDef>()

const clearPin = (pinName:string) => {
    pinMapping.value.get(pinName)!.pinMap = []
    model.value!.PinMaps = []
    pinMapping.value.forEach((v,k) => {
        model.value?.PinMaps.push(...v.pinMap)
    })
}

const editPin = (pinName:string) => {
    editPinName.value = pinName
    curEditPinMapOptIdx.value = []
    curEditPinMap.value = pinMapping.value.get(pinName)?.pinMap
    if (!curEditPinMap.value || curEditPinMap.value?.length == 0) {
        curEditPinMap.value = [new pins.PinMap({
            InputPinName: curEditPin.value?.pinDef.Direction == 1 ? curEditPin.value?.pinDef.Name : '',
            OutputPinName: curEditPin.value?.pinDef.Direction == 0 ? curEditPin.value?.pinDef.Name : '',
            InputDevId: curEditPin.value?.pinDef.Direction == 1 ? curEditPin.value?.devId : 0,
            OutputDevId: curEditPin.value?.pinDef.Direction == 0 ? curEditPin.value?.devId : 0,
            PinBitPair: {
                InputBitStart: 0,
                InputBitEnd: curEditPin.value?.pinDef.Direction == 1 ? curEditPin.value?.pinDef.BitSize - 1 : 0,
                OutputBitStart: 0,
                OutputBitEnd: curEditPin.value?.pinDef.Direction == 0 ? curEditPin.value?.pinDef.BitSize - 1 : 0
            }}),
        ]
    }
    curEditPinMap.value?.forEach(pm => {
        if (curEditPin.value?.pinDef.Direction == 0) {
            pm.OutputDevId = curEditPin.value?.devId
            if (pm.InputDevId > 0) {
                curEditPinMapOptIdx.value.push(`${pm.InputDevId}_${pm.InputPinName}`)
            }
        } else if (curEditPin.value?.pinDef.Direction == 1) {
            pm.InputDevId = curEditPin.value?.devId
            if(pm.OutputDevId > 0) {
                curEditPinMapOptIdx.value.push(`${pm.OutputDevId}_${pm.OutputPinName}`)
            }
        }
    })
    editVerify()
    showEdit.value = true
}

const updateLine = (idx: number) => {
    let value = curEditPinMapOptIdx.value[idx]
    let pin  = pinDefMap.get(value)
    let devId = Number(value.substring(0, value.indexOf('_')))
    let pinName = value.substring(value.indexOf('_') + 1)
    if (pin) {
        if (pin.pinDef.Direction == 1) {
            curEditPinMap.value![idx].InputPinName = pinName
            curEditPinMap.value![idx].PinBitPair!.InputBitStart = 0
            curEditPinMap.value![idx].PinBitPair!.InputBitEnd = pin.pinDef.BitSize - 1
            curEditPinMap.value![idx].InputDevId = devId
            inputStatus.value = 'success'
        } else {
            curEditPinMap.value![idx].OutputPinName = pinName
            curEditPinMap.value![idx].PinBitPair!.OutputBitStart = 0
            curEditPinMap.value![idx].PinBitPair!.OutputBitEnd = pin.pinDef.BitSize - 1
            curEditPinMap.value![idx].OutputDevId = devId
            outputStatus.value = 'success'
        }
    }
}

const editVerify = () => {
    verifyErrorInfo.value = ''
    verifyError.value = false
    inputStatus.value = 'success'
    outputStatus.value = 'success'
    inputBitStatus.value = 'success'
    outputBitStatus.value = 'success'
    if (!curEditPinMap.value || curEditPinMap.value?.length == 0) {
        verifyErrorInfo.value = 'Pin Mapping is required'
        verifyError.value = true
        return false;
    }
    let retValue = true
    curEditPinMap.value.forEach(pm => {
        if (curEditPin.value?.pinDef.Direction == 1) {
            if (!pm.OutputPinName) {
                verifyErrorInfo.value = 'Output Pin is required'
                outputStatus.value = 'error'
                verifyError.value = true
                retValue = false;
                return
            }
            if ((pm.PinBitPair!.OutputBitEnd - pm.PinBitPair!.OutputBitStart) != (pm.PinBitPair!.InputBitEnd - pm.PinBitPair!.InputBitStart)) {
                verifyErrorInfo.value = 'Output Pin Bit Size must be equal to Input Pin Bit Size'
                outputBitStatus.value = 'error'
                verifyError.value = true
                retValue = false;
                return
            }
        } else {
            if (!pm.InputPinName) {
                verifyErrorInfo.value = 'Input Pin is required'
                inputStatus.value = 'error'
                verifyError.value = true
                retValue = false;
                return
            }
            if ((pm.PinBitPair!.OutputBitEnd - pm.PinBitPair!.OutputBitStart) != (pm.PinBitPair!.InputBitEnd - pm.PinBitPair!.InputBitStart)) {
                verifyErrorInfo.value = 'Output Pin Bit Size must be equal to Input Pin Bit Size'
                inputBitStatus.value = 'error'
                verifyError.value = true
                retValue = false;
                return
            }
        }
    })
    return retValue
}

const SaveEdit = () => {
    if (!editVerify()) {
        return false
    }
    pinMapping.value.get(editPinName.value)!.pinMap = curEditPinMap.value!
    model.value!.PinMaps = []
    pinMapping.value.forEach((v,k) => {
        model.value?.PinMaps.push(...v.pinMap)
    })
    showEdit.value = false
}

const curEditPin = computed(() => {
    return pinMapping.value.get(editPinName.value)
})

const getEndIdx = (idx: number) => {
    let pinName = curEditPinMapOptIdx.value[idx]
    if (pinDefMap.get(pinName)) {
        return pinDefMap.get(pinName)!.pinDef.BitSize - 1
    }
    return 0
}

const getCpuStartEndIdx = (pinName: string) => {
    if (cpuPinDefMap.get(pinName)) {
        return [0,cpuPinDefMap.get(pinName)!.BitSize - 1]
    }
    return [0,0]
}

const checkIsFullUsed = (devId: number, pin: pins.PinDef) => {
    if (model.value && model.value.PinMaps) {
        let usedBits = 0
        model.value.PinMaps.forEach(pm => {
            if (pm.InputDevId == devId && pm.InputPinName == pin.Name && pin.Direction == 1) {
                usedBits += (pm.PinBitPair!.InputBitEnd - pm.PinBitPair!.InputBitStart + 1)
            } else if (pm.OutputDevId == devId && pm.OutputPinName == pin.Name && pin.Direction == 0) {
                usedBits += (pm.PinBitPair!.OutputBitEnd - pm.PinBitPair!.OutputBitStart + 1)
            }
        })
        return usedBits >= pin.BitSize
    }
    return false
}

const freeInputPinNames = computed(() => {
    let pinNames:SelectGroupOption[] = []
    if(model.value) {
        Object.entries(model.value).forEach(([k,v]) => {
            if(k === 'CPU') return
            else if (typeof(v) === 'object' && v !== null && v.Enable) {
                if(v.InputPins && v.InputPins.length > 0) {
                    let group:SelectGroupOption = {
                        type: 'group',
                        key: v.TypeName,
                        label: v.TypeName,
                        children: [],
                    };
                    (v.InputPins as pins.PinDef[]).forEach(pin => {
                        if (curEditPinMap.value?.some(pm => pm.OutputPinName == pin.Name) || !checkIsFullUsed(v.Id,pin)) {
                            group.children!.push({
                                label: `${pin.Name}[${pin.BitSize}]`,
                                value: `${v.Id}_${pin.Name}`
                            })
                        }
                    })
                    if(group.children!.length > 0) {
                        pinNames.push(group)
                    }
                }
            }
        })
    }
    return pinNames
})



const freeOutputPinNames = computed(() => {
    let pinNames:SelectGroupOption[] = []
    if(model.value) {
        Object.entries(model.value).forEach(([k,v]) => {
            if(k === 'CPU') return
            else if (typeof(v) === 'object' && v !== null && v.Enable) {
                if(v.OutputPins && v.OutputPins.length > 0) {
                    let group:SelectGroupOption = {
                        type: 'group',
                        key: v.TypeName,
                        label: v.TypeName,
                        children: [],
                    };
                    (v.OutputPins as pins.PinDef[]).forEach(pin => {
                        if (curEditPinMap.value?.some(pm => pm.InputPinName == pin.Name) || !checkIsFullUsed(v.Id, pin)) {
                            group.children!.push({
                                label: `${pin.Name}[${pin.BitSize}]`,
                                value: `${v.Id}_${pin.Name}`
                            })
                        }
                    })
                    if(group.children!.length > 0) {
                        pinNames.push(group)
                    }
                }
            }
        })
    }
    return pinNames
})

const pinMappings = computed(() => {
    let pinMaps:PinMapInfo[] = []
    pinMapping.value.forEach((v,k) => {
        pinMaps.push(v)
    })
    return pinMaps.sort((a,b) => a.pinDef.Name.localeCompare(b.pinDef.Name))
})

const getMappingTdInfo = (pin: PinMapInfo, p: pins.PinMap) => {
    let inputInfo = ""
    let outputInfo = ""
    let inputBits = `[${p.PinBitPair?.InputBitStart}:${p.PinBitPair?.InputBitEnd}]`
    let outputBits = `[${p.PinBitPair?.OutputBitStart}:${p.PinBitPair?.OutputBitEnd}]`
    if (pin.pinDef.Direction == 1) {
        inputInfo = `${pin.pinDef.Name}${inputBits}(CPU)`
        outputInfo = `${p.OutputPinName}${outputBits}(${pinDefMap.get(`${p.OutputDevId}_${p.OutputPinName}`)?.typeName})`
    } else {
        inputInfo = `${p.InputPinName}${inputBits}(${pinDefMap.get(`${p.InputDevId}_${p.InputPinName}`)?.typeName})`
        outputInfo = `${pin.pinDef.Name}${outputBits}(CPU)`
    }
    return `${inputInfo} --> ${outputInfo}`
}

onMounted(() => {
    pinMapping.value.clear()
    pinDefMap.clear()
    cpuPinDefMap.clear()
    if(model.value && model.value.CPU){
        if(!model.value.PinMaps) {
            model.value.PinMaps = []
        }
        Object.entries(model.value).forEach(([k,v]) => {
            if(k === 'CPU') return
            else if (typeof(v) === 'object' && v !== null) {
                if(v.InputPins) {
                    (v.InputPins as pins.PinDef[]).forEach(pin => {
                        pinDefMap.set(`${v.Id}_${pin.Name}`,{
                            pinDef: pin,
                            typeName: v.TypeName
                        })
                    })
                }
                if(v.OutputPins) {
                    (v.OutputPins as pins.PinDef[]).forEach(pin => {
                        pinDefMap.set(`${v.Id}_${pin.Name}`,{
                            pinDef: pin,
                            typeName: v.TypeName
                        })
                    })
                }
            }
        })
        model.value.CPU.InputPins.forEach(pin => {
            cpuPinDefMap.set(pin.Name,pin)
            pinMapping.value.set(pin.Name,{
                devId: model.value!.CPU!.Id,
                pinDef: pin,
                pinMap: model.value!.PinMaps.filter(pm => pin.Name == pm.OutputPinName)
            });
        })
        model.value.CPU.OutputPins.forEach(pin => {
            cpuPinDefMap.set(pin.Name,pin)
            pinMapping.value.set(pin.Name,{
                devId: model.value!.CPU!.Id,
                pinDef: pin,
                pinMap: model.value!.PinMaps.filter(pm => pin.Name == pm.InputPinName)
            });
        })
    }
})


</script>
<style lang="scss" scoped>
.errortips {
    width: 100%;
    text-align: center;
    color: var(--fv-c-text-red);
    margin-top: 1rem;
    font-size: 1rem;
}
td {
    text-align: center;
}
th {
    text-align: center;
}
</style>